/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package it.txt.access.capability.finder;

import it.txt.access.capability.finder.util.CapabilityFileNameFilter;
import it.txt.access.capability.finder.util.URIResourceIDRegexCreator;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import javax.xml.xquery.XQConnection;
import javax.xml.xquery.XQDataSource;
import javax.xml.xquery.XQException;
import javax.xml.xquery.XQPreparedExpression;
import javax.xml.xquery.XQResultSequence;
import org.w3c.dom.Element;

/**
 *
 * @author seccia
 */
public class CapabilityXQuerySaxURISearch {

    private static final Logger LOGGER = Logger.getLogger(CapabilityXQuerySaxURISearch.class.getName());
    private static final String DRIVER_SAX = "net.sf.saxon.xqj.SaxonXQDataSource";
    //This query is used to retrieve the resourceID from the capability
    private static final String RESOURCE_QUERY = ""
            + " declare namespace tns = 'http://schema.capability.access.txt.it';"
            + " declare variable $capabilityDoc as xs:string external;"
            + " let $c := doc($capabilityDoc)/tns:AccessRightsCapability"
            + " let $r := $c/tns:ResourceID"
            + " return $r";
    //This query is used to retrieve the correct capability based
    //on the requested parameters.
    private static final String SEARCH_QUERY = ""
            + " declare namespace tns = 'http://schema.capability.access.txt.it';"
            + " declare variable $capabilityDoc as xs:string external;"
            + " declare variable $subjectID as xs:string external;"
            + " declare variable $resourceID as xs:string external;"
            + " declare variable $permittedAction as xs:string external;"
            + " declare variable $resourceIDReg as xs:string external;"
            + " let $c := doc($capabilityDoc)/tns:AccessRightsCapability"
            + " let $s := $c/tns:Subject/tns:SubjectID"
            + " let $s := fn:normalize-space($s)"
            + " let $s := fn:lower-case($s)"
            + " for $p in $c/tns:AccessRights/tns:AccessRight/tns:PermittedAction"
            + " let $p := fn:normalize-space($p)"
            + " let $p := fn:lower-case($p)"
            + " where $s eq $subjectID"
            + " and $p eq $permittedAction"
            + " and fn:matches($resourceID, $resourceIDReg, 'xi')"
            + " return $c";
    //The directory containing the xml capabilities
    private File capabilitiesDir;
    //The xqdata source connection
    private XQConnection xQConnection;
    //the xq data spurce
    private XQDataSource xQDataSource;
   
    public boolean isAlive() {
        if (xQConnection == null)
            return false;
        else
            return !xQConnection.isClosed();
    }

    public CapabilityXQuerySaxURISearch(File capabilitiesDir) {
        if (!capabilitiesDir.isDirectory()) {
            throw new IllegalArgumentException("No Capabilities Directory specified.");
        }
        try {
            xQDataSource = ((XQDataSource) Class.forName(DRIVER_SAX).newInstance());
            this.capabilitiesDir = capabilitiesDir;
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }

    public ArrayList<CapabilitySearchReturn> doSearchCapability(String subjectID, String resourceID, String permittedAction) {
        
        ArrayList<CapabilitySearchReturn> result = new ArrayList<CapabilitySearchReturn>();
        String userPermittedAction = permittedAction.trim().toLowerCase();
        String userSubject = subjectID.trim().toLowerCase();
        XQPreparedExpression expr_search = null;
        XQResultSequence result_search = null;
        LOGGER.log(Level.INFO, "Searching the best suitable capability [subjectID:: {0}  resourceID:: {1} permittedAction:: {2}]",
                new String[]{subjectID,resourceID,permittedAction});
        try {
            xQConnection = xQDataSource.getConnection();
            
            //Prepare the query expression
            expr_search = xQConnection.prepareExpression(SEARCH_QUERY);
            //bind the static variable to the search query
            QName subjectIDVar = new QName("subjectID");
            expr_search.bindString(subjectIDVar, userSubject, null);
            QName permitedActionVar = new QName("permittedAction");
            expr_search.bindString(permitedActionVar, userPermittedAction, null);
            QName resourceIDActionVar = new QName("resourceID");
            expr_search.bindString(resourceIDActionVar, resourceID, null);
            // loop for each capability file into the specified directory
            for (String fileName : capabilitiesDir.list(new CapabilityFileNameFilter())) {
                // init the resourceid regex and recover the current file
                File capabilityFile = new File(capabilitiesDir, fileName);
                LOGGER.log(Level.INFO, "Searching in file: {0}", capabilityFile.getAbsolutePath());
                //recover the resourceID from the current capability file
                String docResourceIDStr = doSearchResourceIDFromCapability(capabilityFile);
                LOGGER.log(Level.INFO, "ResourceID from the current file: {0}", docResourceIDStr);
                //recover the current regular expression from the resourceID
                String docResourceIDRegex =
                        URIResourceIDRegexCreator.recoverResourceIDRegex(docResourceIDStr);
                // Test the regex
                if (docResourceIDRegex == null) {
                    // Skip if the regular expression creator fail
                    LOGGER.log(Level.WARNING, "No Regex recovered from the "
                            + "current ResourceID: {0}", docResourceIDStr);
                    continue;
                } else {
                    if (URIResourceIDRegexCreator.regularExpressionTest(docResourceIDRegex, resourceID)) {
                        LOGGER.log(Level.INFO, "Evaluating the given resourceID against the regular " +
                    		"expression built from the resourceID reported in the current capability - OK");
                    } else {
                        LOGGER.log(Level.INFO, "Evaluating the given resourceID against the regular " +
                    		"expression built from the resourceID reported in the current capability - ERROR");
                    }
                }
                //bind the current document to the search query
                QName documentVar = new QName("capabilityDoc");
                expr_search.bindString(documentVar, capabilityFile.toURI().toString(), null);
                //bind the current resourceID regex
                QName docResourceIDRegexVar = new QName("resourceIDReg");
                expr_search.bindString(docResourceIDRegexVar, docResourceIDRegex, null);
                // execute the XQuery expression
                result_search = expr_search.executeQuery();
                // process the result (sequence) iteratively
                while (result_search.next()) {
                    // retrieve the current capability
                    Element capability = (Element) result_search.getObject();
                    String capabilityID = capability.getAttribute("AccessRightsCapabilityID");
                    String capabilityIssueIstant = capability.getAttribute("IssueInstant");
                    CapabilitySearchReturn csr = new CapabilitySearchReturn();
                    csr.setCapabilityIssueDate(capabilityIssueIstant);
                    csr.setCapabilityFile(capabilityFile);
                    csr.setCapabilityID(capabilityID);
                    result.add(csr);
                }
                //Close the result set
                result_search.close();
            }
        } catch (XQException ex) {
            // handle the exception in some manner
            LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
        }
        // free the resources whether or not there was an exception
        finally {
            // free all resources allocated for the expression
            if (expr_search != null) {
                try {
                    expr_search.close();
                } catch (XQException ex) {
                    LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
            // free all resources allocated for the result
            if (result_search != null && !result_search.isClosed()) {
                try {
                    result_search.close();
                } catch (XQException ex) {
                    LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
            // free all resources allocated for the connection
            if (xQConnection != null) {
                try {
                    xQConnection.close();
                } catch (XQException xqe) {
                    LOGGER.log(Level.SEVERE, xqe.getMessage(), xqe);
                }
            }
        }
        return result;
    }

    private String doSearchResourceIDFromCapability(File capabilityFile) {

        XQPreparedExpression expr_resource = null;
        XQResultSequence result_resource = null;
        String docResourceIDStr = null;
        try {
            // prepare the expression
            expr_resource = xQConnection.prepareExpression(RESOURCE_QUERY);            
            //bind the current document to the resource query
            QName documentVar = new QName("capabilityDoc");
            expr_resource.bindString(documentVar, capabilityFile.toURI().toString(), null);
            // process the resourceid search result
            result_resource = expr_resource.executeQuery();
            while (result_resource.next()) {
                Element docResourceID = (Element) result_resource.getObject();
                docResourceIDStr = docResourceID.getTextContent();
            }
            result_resource.close();
        }
        catch (XQException ex) {
            // handle the exception in some manner
            LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
        }
        finally {
            // free all resources allocated for the expression
            if (expr_resource != null) {
                try {
                    expr_resource.close();
                } catch (XQException ex) {
                    LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
            // free all resources allocated for the result
            if (result_resource != null && !result_resource.isClosed()) {
                try {
                    result_resource.close();
                } catch (XQException ex) {
                    LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
        }
        return docResourceIDStr;
    }

    public static void main(String[] args) {

        String resourceID = "amqp://service.rabbitmq/ENS?Namespace=Mirafiori%20Robots&Pattern=PLine1.Cell2.Robots.Rb2";
        LOGGER.log(Level.INFO, "Resource ID used into the search: {0}", resourceID);
        File capabilitiesDir = new File("D:\\IoT@Work_DEV\\src\\CapBAC_SVN\\svn\\TXTAccess\\TXTAccess_BIN\\Capabilities");
        String subjectID = "alice@acme.com";
        String permittedAction = "subscribe";
        CapabilityXQuerySaxURISearch cs =
                new CapabilityXQuerySaxURISearch(capabilitiesDir);
        ArrayList<CapabilitySearchReturn> capabilities =
                cs.doSearchCapability(subjectID, resourceID, permittedAction);
        Collections.sort(capabilities, new Comparator<CapabilitySearchReturn>() {
            public int compare(CapabilitySearchReturn o1, CapabilitySearchReturn o2) {
                XMLGregorianCalendar issueDate1 = o1.getCapabilityIssueDateToXmlGregorianCalendar();
                XMLGregorianCalendar isssueDate2 = o2.getCapabilityIssueDateToXmlGregorianCalendar();
                return issueDate1.compare(isssueDate2);
            }
        });
        StringBuilder logMessage = new StringBuilder();
        logMessage.append("Retrieved Capabilities - Sort By Date \n");
        for (CapabilitySearchReturn capabilitySearchReturn : capabilities) {
            logMessage.append("Capability ID: " + capabilitySearchReturn.getCapabilityID() + 
                    " Capability File: " + capabilitySearchReturn.getCapabilityFile().getAbsolutePath());
        }
        LOGGER.log(Level.INFO, logMessage.toString());
    }
}
